/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.dictionaries;

import static ac.jp.u_tokyo.SyncLib.util.Helper.apply;
import static ac.jp.u_tokyo.SyncLib.util.Helper.areEqual;
import static ac.jp.u_tokyo.SyncLib.util.Helper.conflict;
import static ac.jp.u_tokyo.SyncLib.util.Helper.inPlaceApply;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import ac.jp.u_tokyo.SyncLib.IDSync;
import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.ModTypeUnsupportedException;
import ac.jp.u_tokyo.SyncLib.NullMod;
import ac.jp.u_tokyo.SyncLib.PrimMod;
import ac.jp.u_tokyo.SyncLib.StateSync;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.Value2Mod;
import ac.jp.u_tokyo.SyncLib.util.Helper;

public class DynamicGet<K> extends StateSync
{
	boolean _dictOverVal = true;

	boolean _changeKeyFirst = false;

	//	ForMap _initialMap;
	//
	//	K _initialKey;

//	@Override
//	public boolean isModConcerned(int index, Mod mod)
//			throws ModTypeUnsupportedException
//	{
//		if (index == 1)
//		{
//			if (mod instanceof NullMod) return false;
//			if (false == mod instanceof DictMod)
//				throw new ModTypeUnsupportedException();
//			DictMod dmod = (DictMod) mod;
//			return false == dmod.getMod(getKey()) instanceof NullMod;
//
//		}
//		return super.isModConcerned(index, mod);
//	}

	private Object getValue()
	{
		return getDict().get(getKey());
	}

	private Map<K, Object> getDict()
	{
		return (Map<K, Object>) getAttribute(1);
	}

	private K getKey()
	{
		return (K) getAttribute(0);
	}

	private void setKey(K key)
	{
		setAttribute(0, key);
	}

	private void modifyDict(Mod<Map<K, Object>> dict)
	{
		modifyAttribute(1, dict);
	}

	public DynamicGet(boolean dictOverVal, boolean changeKeyFirst/*,
								ForMap initialDict, K initialKey*/)
	{
		super(
				new Object[] { null, null /*initialKey, Helper.deepCopy(initialDict) */});
		_dictOverVal = dictOverVal;
		_changeKeyFirst = changeKeyFirst;
		//		_initialKey = initialKey;
		//		_initialMap = initialDict;
	}

	public int getParaCount()
	{
		return 3;
	}

	private static final DictMod NULLDICTMOD = new DictMod(new HashMap(0));

	private Mod[] internalSync(Mod[] modifications, K oldKey,
			Map<K, Object> nullableDict, Object value)
			throws SynchronizationFailedException
	{
		if (false == modifications[1] instanceof NullMod
				&& false == modifications[1] instanceof DictMod)
			throw new SynchronizationFailedException();

		if (false == modifications[0] instanceof PrimMod
				&& false == modifications[0] instanceof NullMod)
		{
			throw new SynchronizationFailedException();
		}
		//		if (dict == null || oldKey == null)
		//			throw new SynchronizationFailedException();
		Map<K, Object> dict = nullableDict;
		if (dict == null) dict = new HashMap<K, Object>();

		Mod[] result;
		if (_changeKeyFirst)
		{
			try
			{
				result = changeKey(modifications, oldKey, dict, value);
			}
			catch (SynchronizationFailedException e)
			{
				result = changeDictAndValue(modifications, oldKey, dict, value);
			}
		}
		else
		{
			try
			{
				result = changeDictAndValue(modifications, oldKey, dict, value);
			}
			catch (SynchronizationFailedException e)
			{
				result = changeKey(modifications, oldKey, dict, value);
			}
		}

		if (result[1] instanceof NullMod && dict == null)
		{
			result[1] = NULLDICTMOD;
		}
		return result;
	}

	private Mod[] changeKey(Mod[] modifications, K oldKey, Map<K, Object> dict,
			Object value) throws SynchronizationFailedException
	{
		Map<K, Object> newDict = (Map<K, Object>) modifications[1].apply(dict);
		Object newValue = modifications[2].apply(value);

		if (false == modifications[0] instanceof NullMod)
		{
			Object newKey = modifications[0].apply(oldKey);
			if (newKey == null) throw new SynchronizationFailedException();
			if (Helper.areEqual(newDict.get(newKey), newValue))
				return modifications;
			else
				throw new SynchronizationFailedException();
		}

		if (oldKey != null)
			if (areEqual(newValue, newDict.get(oldKey)))
				return new Mod[] {
						(modifications[2] instanceof NullMod ? modifications[0]
								: new PrimMod(oldKey)), modifications[1],
						modifications[2] };

		for (Entry<K, Object> entry : newDict.entrySet())
		{
			if (areEqual(entry.getValue(), newValue))
				return new Mod[] { new PrimMod(entry.getKey()),
						modifications[1], modifications[2] };
		}

		DictMod<K, Object> dictMod;
		if (modifications[0] instanceof NullMod)
			dictMod = NULLDICTMOD;
		else
			dictMod = ((DictMod<K, Object>) modifications[1]);

		if (oldKey != null)
			if (!conflict(dictMod.getMod(oldKey), modifications[2])
					&& Helper.areEqual(newDict.get(oldKey), modifications[2]
							.apply(newDict.get(oldKey))))
				return changeDictAndValueWithFixedKey(new Mod[] {
						new PrimMod<K>(oldKey), modifications[1],
						modifications[2] }, oldKey, dict, value, _dictOverVal);

		for (Entry<K, Object> entry : newDict.entrySet())
		{
			if (!conflict(dictMod.getMod(entry.getKey()), modifications[2])
					&& Helper.areEqual(entry.getValue(), modifications[2]
							.apply(entry.getValue())))
				return changeDictAndValueWithFixedKey(new Mod[] {
						new PrimMod<K>(entry.getKey()), modifications[1],
						modifications[2] }, entry.getKey(), dict, value,
						_dictOverVal);
		}

		Mod[] result = insertNewKey(modifications, dictMod, dict, value);
		if (result != null) return result;

		if (oldKey != null)
			if (!conflict(dictMod.getMod(oldKey), modifications[2]))
				return changeDictAndValueWithFixedKey(
						new Mod[] { new PrimMod(oldKey), modifications[1],
								modifications[2] }, oldKey, dict, value,
						_dictOverVal);

		for (Entry<K, Object> entry : newDict.entrySet())
		{
			if (!conflict(dictMod.getMod(entry.getKey()), modifications[2]))
				return changeDictAndValueWithFixedKey(new Mod[] {
						new PrimMod<K>(entry.getKey()), modifications[1],
						modifications[2] }, entry.getKey(), dict, value,
						_dictOverVal);
		}

		throw new SynchronizationFailedException();

	}

	protected Mod[] insertNewKey(Mod[] modifications,
			DictMod<K, Object> dictMod, Map<K, Object> dict, Object value)
			throws SynchronizationFailedException
	{
		return null;
	}

	private Mod[] changeDictAndValue(Mod[] modifications, K oldKey,
			Map<K, Object> dict, Object value)
			throws SynchronizationFailedException
	{
		Mod modOnKey = modifications[0];
		final K key = apply((Mod<K>) modOnKey, oldKey);
		if (key == null) throw new SynchronizationFailedException();

		return changeDictAndValueWithFixedKey(modifications, key, dict, value,
				_dictOverVal);
	}

	protected Mod[] changeDictAndValueWithFixedKey(Mod[] modifications,
			final K key, Map<K, Object> dict, Object value,
			boolean dictOverValue) throws SynchronizationFailedException
	{
		DictMod<K, Object> dictMod1;
		if (modifications[1] instanceof NullMod)
			dictMod1 = NULLDICTMOD;
		else
			dictMod1 = ((DictMod<K, Object>) modifications[1]);

		Mod mod1 = dictMod1.getMod(key);
		Mod mod2 = modifications[2];

		if (conflict(mod1, mod2)) throw new SynchronizationFailedException();

		Mod vresult = IDSync.mergeWithValue(mod1, mod2, dict.get(key), value,
				dictOverValue);
		Mod dresult = DictMod.replace(dictMod1, key, vresult);
		if (dresult.equals(NULLDICTMOD)
				&& (modifications[1] instanceof NullMod))
			dresult = NullMod.INSTANCE;

		Mod kresult;
		if (dresult.equals(modifications[1])
				&& vresult.equals(modifications[2]))
			kresult = modifications[0];
		else
		{
			kresult = new PrimMod<K>(key);
			assert !kresult.isConflict(modifications[0]);
		}

		Mod[] result = new Mod[] { kresult, dresult, vresult };
		return result;
	}

	public boolean isDictOverVal()
	{
		return _dictOverVal;
	}

	public void setDictOverVal(boolean dictOverVal)
	{
		_dictOverVal = dictOverVal;
	}

	public boolean isChangeKeyFirst()
	{
		return _changeKeyFirst;
	}

	public void setChangeKeyFirst(boolean changeKeyFirst)
	{
		_changeKeyFirst = changeKeyFirst;
	}

	//	public Object[] getInitialValues()
	//	{
	//		return new Object[] { _initialKey, _initialMap,
	//				_initialMap.get(_initialKey) };
	//	}

	public Mod[] resynchronize(Object[] values, Mod[] modifications)
			throws SynchronizationFailedException
	{
		Mod[] result = internalSync(modifications, (K) values[0],
				(Map<K, Object>) values[1], values[2]);

		if (values[1] == null && result[1] instanceof NullMod)
		{
			result[1] = NULLDICTMOD;
		}

		setKey(apply((Mod<K>) result[0], (K) values[0]));
		modifyDict(Value2Mod.findMod(getDict(), (Map<K, Object>) result[1]
				.apply(values[1])));
		return result;
	}

	public Mod[] synchronize(Mod[] modifications)
			throws SynchronizationFailedException
	{
		Mod[] result;
		result = internalSync(modifications, getKey(), getDict(), getValue());

		setKey(inPlaceApply((Mod<K>) result[0], getKey()));
		modifyDict(result[1]);
		return result;
	}

	@Override
	public String toString()
	{
		return "DynamicGet@" + hashCode();
	}

	//	@Override
	//	public void cancelRevertPoint()
	//	{
	//		System.out.println("**************cancelRevertPoint() called.");
	//		System.out.println("**************state:" + getDict() + getValue());
	//		super.cancelRevertPoint();
	//	}
	//
	//	@Override
	//	public void revert()
	//	{
	//		System.out.println("**************revert() called.");
	//		System.out.println("**************state:" + getDict() + getValue());
	//		super.revert();
	//	}
	//
	//	@Override
	//	public void setRevertPoint()
	//	{
	//		System.out.println("**************setRevertPoint() called.");
	//		System.out.println("**************state:" + getDict() + getValue());
	//		super.setRevertPoint();
	//	}

}
